(function () {
    let class2type = {},
        toString = class2type.toString,
        hasOwn = class2type.hasOwnProperty,
        fnToString = hasOwn.toString,
        ObjectFunctionString = fnToString.call(Object),
        getProto = Object.getPrototypeOf;

    // 检测数据类型的通用方法
    const toType = function toType(obj) {
        if (obj == null) return obj + "";
        return typeof obj === "object" || typeof obj === "function" ?
            /^\[object (\w+)\]$/g.exec(toString.call(obj))[1].toLowerCase() :
            typeof obj;
    };

    // 检测是否为函数
    const isFunction = function isFunction(obj) {
        return typeof obj === "function" && typeof obj.nodeType !== "number" &&
            typeof obj.item !== "function";
    };

    // 检测是否为window对象
    const isWindow = function isWindow(obj) {
        return obj != null && obj === obj.window;
    };

    // 检测是否为数组或者类数组
    const isArrayLike = function isArrayLike(obj) {
        let length = !!obj && "length" in obj && obj.length,
            type = toType(obj);
        if (isFunction(obj) || isWindow(obj)) return false;
        return type === "array" || length === 0 ||
            (typeof length === "number" && length > 0 && (length - 1) in obj);
    };

    // 检测是否为纯粹的对象「标准普通对象 或者 直属类是Object」
    const isPlainObject = function isPlainObject(obj) {
        let proto, Ctor;
        if (!obj || toString.call(obj) !== "[object Object]") return false;
        proto = getProto(obj);
        if (!proto) return true;
        Ctor = hasOwn.call(proto, "constructor") && proto.constructor;
        return typeof Ctor === "function" && fnToString.call(Ctor) === ObjectFunctionString;
    };

    // 检测是否为空对象
    const isEmptyObject = function isEmptyObject(obj) {
        if (obj == null) return false;
        if (typeof obj !== "object") return false;
        let keys = Object.keys(obj);
        keys = keys.concat(Object.getOwnPropertySymbols(obj));
        return keys.length === 0;
    };

    // 检测是不是有效数字
    const isNumeric = function isNumeric(obj) {
        let type = toType(obj);
        return (type === "number" || type === "string") && !isNaN(obj);
    };

    // 函数防抖
    const clearTimer = function clearTimer(timer) {
        if (timer) clearTimeout(timer);
        return null;
    };
    const debounce = function debounce(func, wait, immediate) {
        if (typeof func !== 'function') throw new TypeError('func is not a function~');
        if (typeof wait === 'boolean') immediate = wait;
        if (typeof wait !== 'number') wait = 300;
        if (typeof immediate !== "boolean") immediate = false;
        let timer = null;
        return function operate(...params) {
            let now = !timer && immediate,
                result;
            timer = clearTimer(timer);
            timer = setTimeout(() => {
                if (!immediate) func.call(this, ...params);
                timer = clearTimer(timer);
            }, wait);
            if (now) result = func.call(this, ...params);
            return result;
        };
    };

    // 函数节流
    const throttle = function throttle(func, wait) {
        if (typeof func !== 'function') throw new TypeError('func is not a function~');
        if (typeof wait !== 'number') wait = 300;
        let timer = null,
            previous = 0;
        return function operate(...params) {
            let now = +new Date(),
                remaining = wait - (now - previous),
                result;
            if (remaining <= 0) {
                result = func.call(this, ...params);
                previous = +new Date();
                timer = clearTimer(timer);
            } else if (!timer) {
                timer = setTimeout(() => {
                    func.call(this, ...params);
                    previous = +new Date();
                    timer = clearTimer(timer);
                }, remaining);
            }
            return result;
        };
    };

    // 深浅克隆
    const clone = function clone(target, deep, handle) {
        let type = typeof target,
            isArray = Array.isArray(target),
            isPlain = isPlainObject(target),
            ctor,
            keys,
            obj;
        if (typeof deep !== "boolean") deep = false;
        if (!Array.isArray(handle)) handle = [];
        if (handle.includes(target)) return target;
        handle.push(target);
        if (target == null || type !== "object") return target;
        ctor = target.constructor;
        if (!isArray && !isPlain) return new ctor(target);
        keys = Reflect.ownKeys(target);
        obj = new ctor();
        keys.forEach(key => {
            let val = target[key];
            if (deep) {
                obj[key] = clone(val, deep, handle);
                return;
            }
            obj[key] = val;
        });
        return obj;
    };

    // 日期格式化
    const formatTime = function formatTime(time, template) {
        if (typeof time !== "string") {
            time = new Date().toLocaleString('zh-CN', { hour12: false });
        }
        if (typeof template !== "string") {
            template = "{0}年{1}月{2}日 {3}:{4}:{5}";
        }
        let arr = [];
        if (/^\d{8}$/.test(time)) {
            let [, $1, $2, $3] = /^(\d{4})(\d{2})(\d{2})$/.exec(time);
            arr.push($1, $2, $3);
        } else {
            arr = time.match(/\d+/g);
        }
        return template.replace(/\{(\d+)\}/g, (_, $1) => {
            let item = arr[$1] || "00";
            if (item.length < 2) item = "0" + item;
            return item;
        });
    };

    let utils = {
        debounce,
        throttle,
        toType,
        isFunction,
        isWindow,
        isArrayLike,
        isPlainObject,
        isEmptyObject,
        isNumeric,
        clone,
        formatTime
    };

    /* 导出模块 */
    if (typeof module === "object" && typeof module.exports === "object") module.exports = utils;
    if (typeof window !== 'undefined') window.utils = utils;
})();